using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace xiushen.Domain
{
    public enum ResultSortDirection
    {
        Ascending,
        Descending
    }

    public static class IEnumerableExtensions
    {
        /// <summary>
        /// Performs an action on each item in the IEnumerable.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="action"></param>
        public static void ForEach<TEntity>(this IEnumerable<TEntity> enumerable, Action<TEntity> action)
        {
            enumerable.ToList().ForEach(action);
        }

        public static string ToCommaSeparatedString<T>(this IEnumerable<T> enumerable)
        {
            if (enumerable == null || !enumerable.Any())
            {
                return "";
            }
            else
            {
                StringBuilder output = new StringBuilder();

                enumerable.ForEach(e =>
                {
                    if (output.Length > 0)
                    {
                        output.Append(", ");
                    }
                    output.Append(e.ToString());
                });

                return output.ToString();
            }
        }
    }
    public static class ShortExtenstions
    {
        public static bool ToBoolean(this short param)
        {
            return param != 0;
        }
    }
    public static class StringExtensions
    {
        /// <summary>
        /// Checks wheter the string is Null or Empty.
        /// Returns:
        ///     True if the string is Null or Empty.
        ///     False if the string is not Null or not Empty.
        /// </summary>
        /// <param name="param"></param>
        public static bool IsNullOrEmpty(this string param)
        {
            return string.IsNullOrEmpty(param);
        }

        /// <summary>
        /// Checks wheter the string is not Null or Empty.
        /// Returns:
        ///     True if the string is not Null or not Empty.
        ///     False if the string is Null or Empty.
        /// </summary>
        /// <param name="param"></param>
        public static bool IsNotNullOrEmpty(this string param)
        {
            return !string.IsNullOrEmpty(param);
        }

        /// <summary>
        /// Replaces the wildcards in the string.
        /// '*' becomes '%' and '?' becomes '_'.
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string ReplaceWildCards(this string param)
        {
            return param.IsNotNullOrEmpty() ? param.Replace('*', '%').Replace('?', '_') : "";
        }

        public static string ReplaceWildCardsToEmpty(this string param)
        {
            return param.IsNotNullOrEmpty() ? param.Replace("*", string.Empty).Replace("%", string.Empty) : "";
        }

        public static bool HasWildCards(this string param)
        {
            return new[] { "*", "%", "?", "_" }.Any(wc => param.Contains(wc));
        }

        public static string NullIfEmpty(this string param)
        {
            return string.IsNullOrEmpty(param) ? null : param;
        }
    }

    public static class EntityFrameworkExtensions
    {
        /// <summary>
        /// Returns the id of the EntityReference without going to the database.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <typeparam name="TId"></typeparam>
        /// <param name="entityReference"></param>
        /// <returns></returns>
        public static TId Id<TEntity, TId>(this EntityReference<TEntity> entityReference)
            where TEntity : class, IEntity<TId>
        {
            if (entityReference.Value != null)
            {
                return entityReference.Value.Id;
            }

            if (entityReference.EntityKey != null)
            {
                return (TId)entityReference.EntityKey.EntityKeyValues[0].Value;
            }

            return default(TId);
        }

        /// <summary>
        /// Returns the id of the EntityObject without going to the database.
        /// </summary>
        /// <typeparam name="TId"></typeparam>
        /// <param name="entityObject"></param>
        /// <returns></returns>
        public static TId Id<TId>(this EntityObject entityObject)
        {
            return (TId)GetEntityKeyProperty(entityObject).GetValue(entityObject, null);
        }

        /// <summary>
        /// Returns the property name of the primary key property.
        /// </summary>
        /// <param name="entityObject"></param>
        /// <returns></returns>
        public static string EntityKeyPropertyName(this EntityObject entityObject)
        {
            return GetEntityKeyProperty(entityObject).Name;
        }

        ///// <summary>
        ///// Starts a new transaction for the given IsolationLevel on the ObjectContext.
        ///// </summary>
        ///// <param name="objectContext"></param>
        ///// <param name="isolationLevel"></param>
        ///// <returns></returns>
        //public static DbTransaction BeginTransaction(this IObjectContext objectContext, IsolationLevel isolationLevel)
        //{
        //    if (objectContext.Connection.State != ConnectionState.Open)
        //    {
        //        objectContext.Connection.Open();
        //    }

        //    return objectContext.Connection.BeginTransaction(isolationLevel);
        //}

        public static SqlConnection SqlConnection(this IObjectContext objectContext)
        {
            return (SqlConnection)((EntityConnection)objectContext.Connection).StoreConnection;
        }
      
        /// <summary>
        /// Adds an entity to the ObjectContext.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="objectContext"></param>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static TEntity AddObject<TEntity>(this IObjectContext objectContext, TEntity entity)
        {
            objectContext.AddObject(typeof(TEntity).Name, entity);

            return entity;
        }

        public static SqlTransaction SqlTransaction(this IObjectContext objectContext)
        {
            var transaction = (EntityTransaction)objectContext.Transaction;

            if (transaction != null)
            {
                var storeTransactionProperty = transaction.GetType().GetProperty("StoreTransaction",
                                                                                 BindingFlags.Instance |
                                                                                 BindingFlags.NonPublic);

                return (SqlTransaction)storeTransactionProperty.GetValue(transaction, null);
            }

            return null;
        }
        /// <summary>
        /// Clears the ObjectContexts by removing all it's Attached entities.
        /// </summary>
        /// <param name="objectContext"></param>
        public static void Clear(this IObjectContext objectContext)
        {
            foreach (var entity in GetAllEntitiesFromObjectContext(objectContext))
            {
                if (entity != null)
                {
                    objectContext.Detach(entity);
                }
            }
        }

        /// <summary>
        /// Returns an entity from the ObjectContext by EntityKey.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="objectContext"></param>
        /// <param name="entityKey"></param>
        /// <returns></returns>
        public static TEntity GetObjectByKey<TEntity>(this IObjectContext objectContext, EntityKey entityKey)
        {
            return (TEntity)objectContext.GetObjectByKey(entityKey);
        }

        /// <summary>
        /// Checks whether an entity for the EntityKey exists in the ObjectContext.
        /// Returns true if the entity exists, returns false if the entity does not exist.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="objectContext"></param>
        /// <param name="entityKey"></param>
        /// <returns></returns>
        public static bool HasObjectWithKey<TEntity>(this IObjectContext objectContext, EntityKey entityKey) where TEntity : IEntity
        {
            return objectContext.TryGetObjectByKey<TEntity>(entityKey) != null;
        }

        /// <summary>
        /// Tries to return an entity from the ObjectContext by EntityKey.
        /// Returns the entity if the entity exists, returns null if the entity does not exist.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="objectContext"></param>
        /// <param name="entityKey"></param>
        /// <returns></returns>
        public static TEntity TryGetObjectByKey<TEntity>(this IObjectContext objectContext, EntityKey entityKey) where TEntity : IEntity
        {
            object entity = null;
            objectContext.TryGetObjectByKey(entityKey, out entity);
            return (TEntity)entity;
        }

        /// <summary>
        /// Returns all entities in the ObjectContext.
        /// </summary>
        /// <param name="objectContext"></param>
        /// <returns></returns>
        public static List<object> GetAllEntitiesFromObjectContext(this IObjectContext objectContext)
        {
            var allEntities = new List<object>();

            allEntities.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(e => e.Entity));
            allEntities.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Select(e => e.Entity));
            allEntities.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Select(e => e.Entity));
            allEntities.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged).Select(e => e.Entity));

            return allEntities;
        }

        /// <summary>
        /// Creates an EntityKey based on the id of an entity.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <typeparam name="TId"></typeparam>
        /// <param name="objectContext"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public static EntityKey CreateEntityKey<TEntity, TId>(this IObjectContext objectContext, TId id) where TEntity : EntityObject
        {
            var entitySetName = string.Format("{0}.{1}", objectContext.GetType().Name, GetEntitySet<TEntity>(objectContext).Name);
            var entityKeyPropertyName = Activator.CreateInstance<TEntity>().EntityKeyPropertyName();

            return new EntityKey(entitySetName, entityKeyPropertyName, id);
        }



        /// <summary>
        /// Creates an ObjectQuery for the given entity and parameters.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="objectContext"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static ObjectQuery<TEntity> CreateQuery<TEntity>(this IObjectContext objectContext, params ObjectParameter[] parameters)
        {
            return objectContext.CreateQuery<TEntity>("["+GetEntitySet<TEntity>(objectContext).Name+"]", parameters);
        }

        public static EntitySetBase GetEntitySet<TEntity>(this IObjectContext context)
        {
            EntityContainer container =
                    context.MetadataWorkspace
                        .GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
            Type baseType = GetBaseType(typeof(TEntity));
            EntitySetBase entitySet = container.BaseEntitySets
                .Where(item => item.ElementType.Name.Equals(baseType.Name))
                .FirstOrDefault();

            return entitySet;
        }

        private static Type GetBaseType(Type type)
        {
            var baseType = type.BaseType;
            if (baseType != null && baseType != typeof(EntityObject))
            {
                return GetBaseType(type.BaseType);
            }
            return type;
        }
        /// <summary>
        /// Returns the Queryable's generated sql statement.
        /// </summary>
        /// <param name="queryable"></param>
        /// <returns></returns>
        public static string ToTraceString(this IQueryable queryable)
        {
            return ((ObjectQuery)queryable).ToTraceString();
        }

        /// <summary>
        /// Includes a list of paths into the Queryable.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="inclusions"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> queryable, params string[] inclusions)
        {
            var objectQuery = (ObjectQuery<TEntity>)queryable;

            foreach (var inclusion in inclusions)
            {
                objectQuery = objectQuery.Include(inclusion);
            }

            return objectQuery;
        }

        /// <summary>
        /// Includes a typed path into the Queryable.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <typeparam name="TPath"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Include<TEntity, TPath>(this IQueryable<TEntity> queryable, Func<TEntity, TPath> predicate)
            where TEntity : IEntity
            where TPath : IEntity
        {
            var objectQuery = (ObjectQuery<TEntity>)queryable;
            objectQuery = objectQuery.Include(predicate.Method.ReturnType.Name);
            return objectQuery;
        }

        /// <summary>
        /// Adds a where clause with parameters to the Queryable.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="predicate"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> queryable, string predicate, params ObjectParameter[] parameters)
        {
            var objectQuery = (ObjectQuery<TEntity>)queryable;
            var wheredObjectQuery = objectQuery.Where(predicate, parameters);

            return wheredObjectQuery;
        }

        /// <summary>
        /// Adds a where clause with parameters to the Queryable based on a condition.
        /// If the condition evaluates to true, the where clause is added.
        /// If the condition evaluates to false, the where clause is not added.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="condition"></param>
        /// <param name="predicate"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> queryable, bool condition, string predicate, params ObjectParameter[] parameters)
        {
            return condition ? Where(queryable, predicate, parameters) : queryable;
        }

        /// <summary>
        /// Adds a like clause to the Queryable based on a condition where the path is compared to a value.
        /// If the condition evaluates to true, the where clause is added.
        /// If the condition evaluates to false, the where clause is not added.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="condition"></param>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> queryable, bool condition, string path, string value)
        {
            return Where(queryable, condition, string.Format("it.{0} LIKE @{0}", path), new ObjectParameter(path, value));
        }

        /// <summary>
        /// Adds a like clause to the Queryable based on a condition where the path is compared to a value.
        /// If the condition evaluates to true, the where clause is added.
        /// If the condition evaluates to false, the where clause is not added.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="condition"></param>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> queryable, string path, string value)
        {
            return Where(queryable, string.Format("it.{0} LIKE @{0}", path), new ObjectParameter(path, value));
        }

        /// <summary>
        /// Adds a like clause to the Queryable based on a condition where the path is compared to a value.
        /// If the condition evaluates to true, the where clause is added.
        /// If the condition evaluates to false, the where clause is not added.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="condition"></param>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity, TPath>(this IQueryable<TEntity> queryable, bool condition, Func<TEntity, TPath> expression, string value)
        {
            var path = expression.Method.ReturnType.Name;
            return Where(queryable, condition, string.Format("it.{0} LIKE @{0}", path), new ObjectParameter(path, value));
        }

        /// <summary>
        /// Adds an expression to the Queryable based on a condition.
        /// If the condition evaluates to true, the expression is added.
        /// If the condition evaluates to false, the expression is not added.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="condition"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> queryable, bool condition, Expression<Func<TEntity, bool>> expression)
        {
            return condition ? queryable.Where(expression) : queryable;
        }

        /// <summary>
        /// Adds an 'in' clause to the Queryable based on an IEnumerable of matching values.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="valueSelector"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity, TValue>(this IQueryable<TEntity> queryable, Expression<Func<TEntity, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            ParameterExpression p = valueSelector.Parameters.Single();

            if (!values.Any())
            {
                return queryable.Where(e => false);
            }

            var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
            return queryable.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
        }

        public static IQueryable<TEntity> WhereLike<TEntity, TValue>(this IQueryable<TEntity> queryable, Expression<Func<TEntity, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            ParameterExpression p = valueSelector.Parameters.Single();

            if (!values.Any())
            {
                return queryable.Where(e => false);
            }

            MethodInfo method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });

            var equals = values.Select(value => (Expression)Expression.Call(valueSelector.Body, method, Expression.Constant(value, typeof(TValue))));

            var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
            return queryable.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
        }

        public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> queryable, string expression, ResultSortDirection direction)
        {
            //var objectQuery = (ObjectQuery<TEntity>)queryable;

            //if (direction == ResultSortDirection.Ascending)
            //{
            //    return objectQuery.OrderBy("it." + expression + " ASC");
            //}
            //else
            //{
            //    return objectQuery.OrderBy("it." + expression + " DESC");
            //}

            return OrderBy(queryable, new[] { expression }, direction);
        }

        public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> queryable, string[] expressions, ResultSortDirection direction)
        {
            if (expressions == null || expressions.Length == 0)
            {
                return queryable;
            }

            var orderByMethodName = direction == ResultSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
            var thenByMethodName = direction == ResultSortDirection.Ascending ? "ThenBy" : "ThenByDescending";

            var query = queryable;
            var elementParameter = Expression.Parameter(queryable.ElementType, String.Empty);

            for (int i = 0; i < expressions.Length; i++)
            {
                var expression = expressions[i];
                var expressionProperties = expression.Split('.');
                MemberExpression property = null;

                foreach (var expressionProperty in expressionProperties)
                {
                    if (property == null)
                    {
                        property = Expression.Property(elementParameter, expressionProperty);
                    }
                    else
                    {
                        property = Expression.Property(property, expressionProperty);
                    }
                }

                if (property == null)
                {
                    throw new InvalidOperationException("You should sort at least on one property");
                }

                var lambda = Expression.Lambda(property, elementParameter);

                if (i == 0)
                {
                    Expression methodCallExpression = Expression.Call(typeof(Queryable), orderByMethodName, new[] { queryable.ElementType, property.Type }, query.Expression, Expression.Quote(lambda));
                    query = query.Provider.CreateQuery<TEntity>(methodCallExpression);
                }
                else
                {
                    Expression methodCallExpression = Expression.Call(typeof(Queryable), thenByMethodName, new[] { queryable.ElementType, property.Type }, query.Expression, Expression.Quote(lambda));
                    query = query.Provider.CreateQuery<TEntity>(methodCallExpression);
                }
            }

            return query;
        }

        public static IOrderedQueryable<TEntity> OrderBy<TEntity, TKey>(this IQueryable<TEntity> queryable, Expression<Func<TEntity, TKey>> expression, ResultSortDirection direction)
        {
            return direction == ResultSortDirection.Ascending ? queryable.OrderBy(expression) : queryable.OrderByDescending(expression);
        }

        public static IOrderedQueryable<TEntity> ThenBy<TEntity, TKey>(this IOrderedQueryable<TEntity> queryable, Expression<Func<TEntity, TKey>> expression, ResultSortDirection direction)
        {
            return direction == ResultSortDirection.Ascending ? queryable.ThenBy(expression) : queryable.ThenByDescending(expression);
        }

        //public static IQueryable<T> SortBy<T>(this IQueryable<T> queryable, string propertyName, ResultSortDirection direction)
        //{
        //    if (queryable == null)
        //    {
        //        throw new ArgumentNullException("queryable");
        //    }

        //    if (String.IsNullOrEmpty(propertyName))
        //    {
        //        return queryable;
        //    }

        //    ParameterExpression parameter = Expression.Parameter(queryable.ElementType, String.Empty);
        //    MemberExpression property = Expression.Property(parameter, propertyName);
        //    LambdaExpression lambda = Expression.Lambda(property, parameter);

        //    string methodName = direction == ResultSortDirection.Ascending ? "OrderBy" : "OrderByDescending";

        //    Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
        //                                        new Type[] { queryable.ElementType, property.Type },
        //                                        queryable.Expression, Expression.Quote(lambda));

        //    return queryable.Provider.CreateQuery<T>(methodCallExpression);
        //}


        /// <summary>
        /// Sums the values from the expression, and returns 0 in case the result is NULL.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static decimal SumIfExists<TEntity>(this IQueryable<TEntity> queryable, Expression<Func<TEntity, decimal>> expression)
        {
            decimal result = 0;

            try
            {
                result = queryable.Sum(expression);
            }
            catch (InvalidOperationException) { }

            return result;
        }
        public static IQueryable<T> Paging<T>(this IQueryable<T> query, int currentPage, int defaultPage, int pageSize)
        {
            return query
                .Skip((currentPage - defaultPage) * pageSize)
                .Take(pageSize);
        }
        private static PropertyInfo GetEntityKeyProperty(EntityObject entityObject)
        {
            foreach (var property in entityObject.GetType().GetProperties())
            {
                foreach (var attribute in property.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false))
                {
                    var edmPropertyAttribute = (EdmScalarPropertyAttribute)attribute;
                    if (edmPropertyAttribute.EntityKeyProperty)
                    {
                        return property;
                    }
                }
            }

            throw new InvalidOperationException("Entity " + entityObject.GetType().Name + " does not have a entity key property.");
        }
    }

    public static class IntegerExtensions
    {
        /// <summary>
        /// Returns the integer when it's a valid Id, or NULL if it's not.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static int? ToNulllableId(this int id)
        {
            return (id > 0) ? (int?)id : null;
        }

    }

    public static class DateTimeExtensions
    {
        public static bool IsAfter(this DateTime theDate, DateTime date)
        {
            return (theDate > date);
        }

        public static bool IsBefore(this DateTime theDate, DateTime date)
        {
            return (theDate < date);
        }

    }
    public static class QueryableExtensions
    {
        public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
        {
            return new ProjectionExpression<TSource>(source);
        }
    }

    public class ProjectionExpression<TSource>
    {
        private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>();

        private readonly IQueryable<TSource> _source;

        public ProjectionExpression(IQueryable<TSource> source)
        {
            _source = source;
        }

        public IQueryable<TDest> To<TDest>()
        {
            var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>();

            return _source.Select(queryExpression);
        }

        private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
        {
            var key = GetCacheKey<TDest>();

            return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
        }

        private static Expression<Func<TSource, TDest>> BuildExpression<TDest>()
        {
            var sourceProperties = typeof(TSource).GetProperties();
            var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite);
            var parameterExpression = Expression.Parameter(typeof(TSource), "src");

            var bindings = destinationProperties
                                .Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties))
                                .Where(binding => binding != null);

            var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), Convert(bindings)), parameterExpression);

            var key = GetCacheKey<TDest>();

            ExpressionCache.Add(key, expression);

            return expression;
        }
        private static IEnumerable<MemberBinding> Convert(IEnumerable<MemberAssignment> bindings)
        {
            return bindings.Select(r => (MemberBinding)r);
        }

        private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties)
        {
            var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name.ToUpper() == destinationProperty.Name.ToUpper());

            if (sourceProperty != null)
            {
                return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
            }

            var propertyNames = SplitCamelCase(destinationProperty.Name);

            if (propertyNames.Length == 2)
            {
                sourceProperty = sourceProperties.FirstOrDefault(src => src.Name.ToUpper() == propertyNames[0].ToUpper());

                if (sourceProperty != null)
                {
                    var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name.ToUpper() == propertyNames[1].ToUpper());

                    if (sourceChildProperty != null)
                    {
                        return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty));
                    }
                }
            }

            return null;
        }

        private static string GetCacheKey<TDest>()
        {
            return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
        }

        private static string[] SplitCamelCase(string input)
        {
            return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
        }
    }
}
